function Compile(el, vm) {
  this.$vm = vm;
  // 判断el是否是元素节点
  this.$el = this.isElementNode(el) ? el : document.querySelector(el);

  if (this.$el) {
    // 模板解析三步
    // 1. 将节点装换成文档碎片节点
    this.$fragment = this.node2Fragment(this.$el);
    // 2. 编译模板
    this.init();
    // 3. 将编译后的模板放入el中生效
    this.$el.appendChild(this.$fragment);
  }
}

Compile.prototype = {
  node2Fragment: function (el) {
    var fragment = document.createDocumentFragment(),
      child;

    // 将原生节点拷贝到fragment
    while ((child = el.firstChild)) {
      fragment.appendChild(child);
    }

    return fragment;
  },

  init: function () {
    this.compileElement(this.$fragment);
  },

  compileElement: function (el) {
    // 取出当前元素的所有子节点
    var childNodes = el.childNodes,
      me = this;

    // 装换成真数组进行遍历
    [].slice.call(childNodes).forEach(function (node) {
      // 取出子节点的文本内容
      var text = node.textContent;
      // 定义正则：用来匹配双大扩号表达式或插值语法
      var reg = /\{\{(.*)\}\}/;

      // 首先判断子节点是否是元素节点
      if (me.isElementNode(node)) {
        // 编译元素：解析指令语法
        me.compile(node);

        // 然后再判断子节点是否是文本节点 并且 里面的文本内容是插值语法（通过正则匹配）
      } else if (me.isTextNode(node) && reg.test(text)) {
        // 解析，传入两个参数
        // node 就是当前子节点
        // RegExp.$1 匹配到的表达式
        me.compileText(node, RegExp.$1);
      }

      // 判断当前子节点是否还有子节点
      // 如果有就递归调用，对子节点的子节点在进行编译
      // 确保所有节点都要进行编译
      if (node.childNodes && node.childNodes.length) {
        me.compileElement(node);
      }
    });
  },

  compile: function (node) {
    // 提取当前元素的所有属性节点
    var nodeAttrs = node.attributes,
      me = this;

    // 进行遍历
    [].slice.call(nodeAttrs).forEach(function (attr) {
      /*
        attr 是属性节点
          attr.name 是属性名
          attr.value 是属性值
      */

      // 获取属性名 v-on:click
      var attrName = attr.name;
      // 判断当前属性是否是指令（v-）
      if (me.isDirective(attrName)) {
        // 获取属性值
        var exp = attr.value;
        // 截取属性名得到 on:click
        var dir = attrName.substring(2);
        // 判断指令是否是事件指令(on)
        if (me.isEventDirective(dir)) {
          // 处理事件指令
          compileUtil.eventHandler(node, me.$vm, exp, dir);
          // 普通指令
        } else {
          compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
        }

        // 将编译好的指令属性给移除掉
        node.removeAttribute(attrName);
      }
      
    });
  },

  compileText: function (node, exp) {
    compileUtil.text(node, this.$vm, exp);
  },

  isDirective: function (attr) {
    return attr.indexOf("v-") == 0;
  },

  isEventDirective: function (dir) {
    return dir.indexOf("on") === 0;
  },

  isElementNode: function (node) {
    return node.nodeType == 1;
  },

  isTextNode: function (node) {
    return node.nodeType == 3;
  },
};

// 指令处理集合
var compileUtil = {
  text: function (node, vm, exp) {
    this.bind(node, vm, exp, "text");
  },

  html: function (node, vm, exp) {
    this.bind(node, vm, exp, "html");
  },

  model: function (node, vm, exp) {
    // 更新元素的value值
    this.bind(node, vm, exp, "model");

    var me = this,
      val = this._getVMVal(vm, exp);

    node.addEventListener("input", function (e) {
      var newValue = e.target.value;
      if (val === newValue) {
        return;
      }

      me._setVMVal(vm, exp, newValue);
      val = newValue;
    });
  },

  class: function (node, vm, exp) {
    this.bind(node, vm, exp, "class");
  },

  /**
   *
   * @param {*} node 当前节点
   * @param {*} vm vm实例
   * @param {*} exp 表达式
   * @param {*} dir 指令
   */
  bind: function (node, vm, exp, dir) {
    // bind方法的this指向 compileUtil
    // 根据dir取出对应的updater函数
    // updater就是一些更新DOM元素的方法
    var updaterFn = updater[dir + "Updater"];

    // 调用updater函数来更新DOM元素
    // 更新DOM元素之前首先从vm要读取到表达式的值
    updaterFn && updaterFn(node, this._getVMVal(vm, exp));

    // vm 实例
    // exp 表达式 msg
    // callback 回调函数 里面通过闭包保存了更新DOM元素的方法（更新用户界面方法）
    new Watcher(vm, exp, function (value, oldValue) {
      updaterFn && updaterFn(node, value, oldValue);
    });
  },

  // 事件处理
  // node 当前元素
  // vm vm实例
  // exp 指令表达式 handleClick
  // dir 指令 on:click
  eventHandler: function (node, vm, exp, dir) {
    // 切割指令得到事件名
    var eventType = dir.split(":")[1],
      // 从vm上methods中找到表达式对应的回调函数
      fn = vm.$options.methods && vm.$options.methods[exp];

    if (eventType && fn) {
      // 绑定事件监听
      // 将事件回调函数的this指向vm
      node.addEventListener(eventType, fn.bind(vm), false);
    }
  },

  // 获取表达式的值
  _getVMVal: function (vm, exp) {
    var val = vm._data;
    exp = exp.split(".");
    exp.forEach(function (k) {
      val = val[k];
    });
    return val;
  },

  // 设置表达式的值
  _setVMVal: function (vm, exp, value) {
    var val = vm._data;
    exp = exp.split("."); // ['person', 'name']

    exp.forEach(function (k, i) {
      // 非最后一个key，更新val的值
      if (i < exp.length - 1) {
        // val = data[person] --> person
        val = val[k];
      } else {
        // person[name] = value
        val[k] = value;
      }
    });
  },
};

var updater = {
  textUpdater: function (node, value) {
    // 更新元素的文本为插值语法表达式的值
    // 到此，插值语法解析就结束了
    node.textContent = typeof value == "undefined" ? "" : value;
  },

  htmlUpdater: function (node, value) {
    node.innerHTML = typeof value == "undefined" ? "" : value;
  },

  classUpdater: function (node, value, oldValue) {
    var className = node.className;
    className = className.replace(oldValue, "").replace(/\s$/, "");

    var space = className && String(value) ? " " : "";

    node.className = className + space + value;
  },

  modelUpdater: function (node, value, oldValue) {
    node.value = typeof value == "undefined" ? "" : value;
  },
};
